#pragma once

#include "indicator_common.h"

namespace mdtx::online::indicator
{

    // miscellaneous indicators, could be useful, could be niche, could be holy grail

    class wad
    {
    private:
        double preclose, val;

        double insert(double high, double low, double close)
        {
            double ad = 0.0;
            if (close > preclose)
            {
                ad = close - std::min(preclose, low);
            }
            else if (close < preclose)
            {
                ad = close - std::max(preclose, high);
            }
            val += ad;
            return val;
        }

    public:
        wad()
            : preclose(0.0),
              val(0.0)
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt high, InputIt high_last, InputIt low, InputIt close, OutputIt output)
        {
            while (high != high_last)
            {
                *output++ = insert(*high++, *low++, *close++);
            }
        }

        void operator()(double high, double low, double close, double &output)
        {
            output = insert(high, low, close);
        }
    };

    class volidx
    {
    private:
        bool init;
        double pvi, nvi;
        double lastv, lastp;
        void insert(double p, double v)
        {
            if (init)
            {
                if (lastv > v)
                {
                    pvi += pvi * ((p - lastp) / lastp);
                }
                else if (lastv < v)
                {
                    nvi += nvi * ((p - lastp) / lastp);
                }
            }
            else
            {
                init = true;
            }
            lastp = p;
            lastv = v;
        }

    public:
        volidx(int period, double initialise = 1000.0)
            : init(false),
              pvi(initialise),
              nvi(initialise),
              lastv(0.0),
              lastp(0.0)
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt price, InputIt price_last, InputIt vol, OutputIt output_pvi, OutputIt output_nvi)
        {
            while (price != price_last)
            {
                insert(*price++, *vol++);
                *output_pvi++ = pvi;
                *output_nvi++ = nvi;
            }
        }

        void operator()(double price, double vol, double &output_pvi, double &output_nvi)
        {
            insert(price, vol);
            output_pvi = pvi;
            output_nvi = nvi;
        }
    };

    class psar
    {
    private:
        bool init;
        bool uptrend;
        double acc, acc0, accmax, sar, ep;
        void insert(double high, double low)
        {
            if (init)
            {
                if (uptrend)
                {
                    if (low < sar)
                    {
                        uptrend = false;
                        sar = ep;
                        acc = 0.0;
                        ep = low;
                    }
                    else
                    {
                        ep = std::max(ep, high);
                        acc = std::min(acc + acc0, accmax);
                        sar += acc * (ep - sar);
                    }
                }
                else
                {
                    if (high > sar)
                    {
                        uptrend = true;
                        sar = ep;
                        acc = 0.0;
                        ep = high;
                    }
                    else
                    {
                        ep = std::min(ep, low);
                        acc = std::min(acc + acc0, accmax);
                        sar += acc * (ep - sar);
                    }
                }
            }
            else
            {
                std::random_device rdev;
                std::default_random_engine reng(rdev());
                std::uniform_int_distribution<> runif(0, 1);
                if (runif(reng))
                {
                    uptrend = true;
                    sar = low;
                    ep = high;
                }
                {
                    uptrend = false;
                    sar = high;
                    ep = low;
                }
                init = true;
            }
        }

    public:
        psar(double acc_init = 0.02, double acc_max = 0.2)
            : init(false),
              acc(0.0),
              acc0(acc_init),
              accmax(acc_max)
        {
        }

        template <class InputIt, class OutputIt, class UptrendIt>
        void operator()(InputIt high, InputIt high_last, InputIt low, OutputIt output_psar, UptrendIt output_uptrend)
        {
            while (high != high_last)
            {
                insert(*high++, *low++);
                *output_psar++ = sar;
                *output_uptrend++ = uptrend;
            }
        }

        void operator()(double high, double low, double &output_psar, bool &output_uptrend)
        {
            insert(high, low);
            output_psar = sar;
            output_uptrend = uptrend;
        }
    };

    class obv
    {
    private:
        bool init;
        double vol, last;

        double insert(double p, double v)
        {
            if (init)
            {
                if (p = last)
                {
                    vol = 0.0;
                }
                else if (p < last)
                {
                    vol -= v;
                }
                else
                {
                    vol += v;
                }
            }
            else
            {
                init = true;
            }
            last = p;
            return vol;
        }

    public:
        obv()
            : init(false),
              vol(0.0),
              last(0.0)
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt price, InputIt price_last, InputIt vol, OutputIt output)
        {
            while (price != price_last)
            {
                *output++ = insert(*price++, *vol++);
            }
        }

        void operator()(double price, double vol, double &output)
        {
            output = insert(price, vol);
        }
    };

    class ad
    {
    private:
        mdtx::online::updater::sum_updater<MDTX_INDICATOR_KAHAN> dsum;

        double insert(double high, double low, double close, double vol)
        {
            double d;
            double hl = high - low;
            if (hl != 0.0)
            {
                d = ((close - low) - (high - close)) / hl * vol;
                dsum.insert(d);
            }
            return dsum.s;
        }

    public:
        friend class adosc;

        template <class InputIt, class OutputIt>
        void operator()(InputIt high, InputIt high_last, InputIt low, InputIt close, InputIt vol, OutputIt output)
        {
            while (high != high_last)
            {
                *output++ = insert(*high++, *low++, *close++, *vol++);
            }
        }

        void operator()(double high, double low, double close, double vol, double &output)
        {
            output = insert(high, low, close, vol);
        }
    };

    class adosc
    {
    private:
        mdtx::online::indicator::ad adx;
        mdtx::online::updater::ema_updater<MDTX_INDICATOR_KAHAN> fast_ma, slow_ma;

        double insert(double high, double low, double close, double vol)
        {
            double x;
            x = adx.insert(high, low, close, vol);
            fast_ma.insert(x);
            slow_ma.insert(x);
            return (fast_ma.m - slow_ma.m) / slow_ma.m;
        }

    public:
        adosc(int fast_period, int slow_period)
            : fast_ma(2.0 / (std::min(fast_period, slow_period) + 1)),
              slow_ma(2.0 / (std::max(fast_period, slow_period) + 1))
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt high, InputIt high_last, InputIt low, InputIt close, InputIt vol, OutputIt output)
        {
            while (high != high_last)
            {
                *output++ = insert(*high++, *low++, *close++, *vol++);
            }
        }

        void operator()(double high, double low, double close, double vol, double &output)
        {
            output = insert(high, low, close, vol);
        }
    };

    class rollsum
    {
    private:
        boost::circular_buffer<double> xbuf;
        mdtx::online::updater::sum_updater<MDTX_INDICATOR_KAHAN> xsum;

        double insert(double x)
        {
            if (xbuf.full())
            {
                xsum.roll(x, xbuf.back());
            }
            else
            {
                xsum.insert(x);
            }
            xbuf.push_front(x);
            return xsum.s;
        }

    public:
        friend class cmo;
        friend class mfi;

        explicit rollsum(int period)
            : xbuf(period)
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    class cmo
    {
    private:
        bool init;
        rollsum up, down;
        double last;

        double insert(double x)
        {
            double sum_up, sum_down;
            if (init)
            {
                if (x > last)
                {
                    sum_up = up.insert(x - last);
                    sum_down = down.insert(MDTX_INDICATOR_EPS);
                }
                else if (x < last)
                {
                    sum_up = up.insert(MDTX_INDICATOR_EPS);
                    sum_down = down.insert(last - x);
                }
                else
                {
                    sum_up = up.insert(MDTX_INDICATOR_EPS);
                    sum_down = down.insert(MDTX_INDICATOR_EPS);
                }
            }
            else
            {
                init = true;
            }
            last = x;
            return 100.0 * ((sum_up - sum_down) / (sum_up + sum_down));
        }

    public:
        explicit cmo(int period)
            : init(false),
              up(period),
              down(period),
              last(0.0)
        {
            up.insert(MDTX_INDICATOR_EPS);
            down.insert(MDTX_INDICATOR_EPS);
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt first, InputIt last, OutputIt output)
        {
            while (first != last)
            {
                *output++ = insert(*first++);
            }
        }

        void operator()(double input, double &output)
        {
            output = insert(input);
        }
    };

    class mfi
    {
    private:
        bool init;
        rollsum up, down;
        double last;

        double insert(double p, double tnvr)
        {
            double sum_up, sum_down;
            if (init)
            {
                if (p > last)
                {
                    sum_up = up.insert(tnvr);
                    sum_down = down.insert(MDTX_INDICATOR_EPS);
                }
                else if (p < last)
                {
                    sum_up = up.insert(MDTX_INDICATOR_EPS);
                    sum_down = down.insert(tnvr);
                }
                else
                {
                    sum_up = up.insert(MDTX_INDICATOR_EPS);
                    sum_down = down.insert(MDTX_INDICATOR_EPS);
                }
            }
            else
            {
                init = true;
            }
            last = p;
            return 100.0 * ((sum_up - sum_down) / (sum_up + sum_down));
        }

    public:
        explicit mfi(int period)
            : init(false),
              up(period),
              down(period),
              last(0.0)
        {
            up.insert(MDTX_INDICATOR_EPS);
            down.insert(MDTX_INDICATOR_EPS);
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt close_first, InputIt close_last, InputIt turnover, OutputIt output)
        {
            while (close_first != close_last)
            {
                *output++ = insert(*close_first++, *turnover++);
            }
        }

        void operator()(double close, double turnover, double &output)
        {
            output = insert(close, turnover);
        }
    };

    class cvi
    {
        boost::circular_buffer<double> rbuf;
        mdtx::online::updater::ema_updater<MDTX_INDICATOR_KAHAN> mr;

        double insert(double high, double low)
        {
            double v = 0.0;
            mr.insert(high - low);
            if (!rbuf.empty())
            {
                v = (mr.m - rbuf.back()) / rbuf.back() * 100.0;
            }
            rbuf.push_front(mr.m);
            return v;
        }

    public:
        explicit cvi(int period)
            : rbuf(period),
              mr(2.0 / (period + 1))
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt high, InputIt high_last, InputIt low, OutputIt output)
        {
            while (high != high_last)
            {
                *output++ = insert(*high++, *low++);
            }
        }

        void operator()(double high, double low, double &output)
        {
            output = insert(high, low);
        }
    };

    class bop
    {
        double insert(double open, double high, double low, double close)
        {
            return (close - open) / (high - low);
        }

    public:
        template <class InputIt, class OutputIt>
        void operator()(InputIt open, InputIt open_last, InputIt high, InputIt low, InputIt close, OutputIt output)
        {
            while (open != open_last)
            {
                *output++ = insert(*open++, *high++, *low++, *close++);
            }
        }

        void operator()(double open, double high, double low, double close, double &output)
        {
            output = insert(open, high, low, close);
        }
    };

    // very weird indicator that units of matrices don't make sense
    class emv
    {
        double last, vscale;
        bool init;

        double insert(double high, double low, double vol)
        {
            double typ_price = (high + low) / 2.0;
            double rate = vol * vscale / (high - low);
            double ans = 0.0;
            if (init)
            {
                ans = (typ_price - last) / rate;
            }
            else
            {
                init = true;
            }
            last = typ_price;
            return ans;
        }

    public:
        explicit emv(double vol_scale = 0.0001)
            : last(0.0),
              vscale(vol_scale),
              init(false)
        {
        }

        template <class InputIt, class OutputIt>
        void operator()(InputIt high, InputIt high_last, InputIt low, InputIt vol, OutputIt output)
        {
            while (high != high_last)
            {
                *output++ = insert(*high++, *low++, *vol++);
            }
        }

        void operator()(double high, double low, double vol, double &output)
        {
            output = insert(high, low, vol);
        }
    };

}
